package com.glepoch.springbootsecuritymybatis.compoment.aspect;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.glepoch.springbootsecuritymybatis.bean.RespResultVO;
import com.glepoch.springbootsecuritymybatis.compoment.constant.Constant;
import com.glepoch.springbootsecuritymybatis.compoment.constant.statuscode.GlobalStatusCode;
import com.glepoch.springbootsecuritymybatis.compoment.execption.impl.CommonExecption;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;

/*
 * 功能：数据查询与数据导出公用方法实现数据导出excel
 * 底层框架：底层使用了阿里巴巴的easyExcel，请自行学习easyExcel数据实体类的定义注解
 * 规范：
 * 1.请求url:url后面加上?exportFlag=1  字典：1为导出 其它为查询
 * 2.方法上加注解：@RequestMapping @PostMapping @GetMapping
 * 3.定义数据实体类：在数据实体类中增加表头定义注解属性。底层使用了阿里巴巴的easyExcel，请自行学习easyExcel数据实体类的定义注解
 * 4.返回数据类型：返回结果必须为List类型数据或RespResultVO和它的子类对象且data属性设置List类型数据（List必须指定泛型为步骤3中定义的实体类）,无法理解先看步骤5再回来看此步骤
 * 5.选择方法一和方法二其中一种实现
 * 方法一：根据方法返值类型最内层<>来获取表头实体类,无法指定表名称
 *       示例1：
 *       public RespResultVO<List<StudentEntity>> testExport(){
 *           return data;
 *       }
 *       示例2：
 *       public List<StudentEntity> testExport(){
 *           return data;
 *       }
 * 方法二：通过返回对象手动指定表名和表头实体类，与方法返回值类型定义无关。
 *       public Object testExport(){
 *           RespResultVO<List<StudentEntity>> v = new RespResultVO(200, "成功", data);
 *           v.setSheetName("学生");  //指定sheet名称
 *           v.setSheetHeadClazz(StudentEntity.class); //指定表头实体类
 *           return v;
 *        }
 * */
@Slf4j
@Aspect
@Component
public class ExportSingleSheelExcelAspect {

    private static final Integer SHEET_NO = 0;//生成sheet的序号（位置）
    private static final String EXPORTFLAG = "exportFlag";//导出标识字段key
    private static final String[] BASIC_DATA_TYPE = {"java.lang", "java.util.Objects"};//需要排查的数据类型


    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getLog() {
    }

    @Around(value = "requestLog() || postLog() || getLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //调用目标方法
        Object o = null;//方法返回值
        o = joinPoint.proceed();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return o;
        }
        HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        //判断请求参数中是否存在导出标识字段（exportFlag）
        Integer exportFlag = null;
        Object exportFlagObj = request.getParameter(EXPORTFLAG);
        Object txcode = request.getParameter("txcode");
        //排除掉iwap的导出请求
        if(!StringUtils.isEmpty(txcode)){
            return o;
        }
        if (exportFlagObj != null) {
            try {
                exportFlag = Integer.valueOf(exportFlagObj.toString());
            } catch (Exception e) {
                throw new CommonExecption(GlobalStatusCode.EXPORTFLAG_MUST_NUMBER);
            }
        }
        if (Constant.EXPORT_STATUS != exportFlag)
            return o;

        //需要导出的情况下，把数据需要导出的数据提取出来并且转换为LIST
        List expData = null;//需要导出的数据
        RespResultVO respData = null;
        if (o instanceof List) {
            expData = (List) o;
        } else if (o instanceof RespResultVO) {
            respData = (RespResultVO) o;
            if (respData.getData() instanceof List)
                expData = (List) respData.getData();
        } else
            throw new CommonExecption(GlobalStatusCode.DATA_MUST_LIST);
        if (expData == null || expData.size() == 0)
            throw new CommonExecption(GlobalStatusCode.DATA_MUST_NOT_NULL);
        //获取返回结果类型中的实体实体类型（导出实体类）
        Class<?> targetClazz = null;//导出excel表头配置类
        String sheetName = null;//sheet名称
        //在返回结果中获取
        if (respData != null) {
            targetClazz = respData.getSheetHeadClazz();
            sheetName = respData.getSheetName();
        }
        sheetName = StringUtils.isEmpty(sheetName) ? "Sheet1" : sheetName;
        //在返回类型参数中获取
        if (targetClazz == null) {
            //截取返回参数全限制名最内层泛型值<>
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            Method m = ms.getMethod();
            Type grt = m.getGenericReturnType();
            String fullTypeName = grt.getTypeName();
            int i = fullTypeName.lastIndexOf("<");
            if (-1 == i)
                throw new CommonExecption(GlobalStatusCode.NOT_TYPE_ARG);
            String fullTypeNameSubLeft = fullTypeName.substring(i + 1);
            int j = fullTypeNameSubLeft.indexOf(">");
            String typeName = fullTypeNameSubLeft.substring(0, j);
            //泛型最内存必须为一个值才能进行导出
            if (typeName.contains(",")) {
                throw new CommonExecption(GlobalStatusCode.MUST_ONLY_ONE_ARG);
            }
            try {
                targetClazz = Class.forName(typeName);
            } catch (ClassNotFoundException e) {
                log.error("表头配置类转换错误！");
            }
            Object dataObj = expData.get(0);
            String dataObjClazz = dataObj.getClass().getName();
            if (!Objects.equals(typeName, dataObjClazz)) {
                throw new CommonExecption(GlobalStatusCode.TITLE_BEAN_NOT_THE_DATA_OBJ);
            }
            for (String s : BASIC_DATA_TYPE) {
                if (typeName.startsWith(s))
                    throw new CommonExecption(GlobalStatusCode.MUST_NOT_BASIC_DATA_TYPE);
            }
        }
        //导出处理过程
        if (null != targetClazz) {
            log.info("开始对返回结果进行导出处理!");
            OutputStream os = null;
            try {
                os = response.getOutputStream();
                ExcelWriter excelWriter = EasyExcel.write(os).build();
                WriteSheet sheet1 = EasyExcel.writerSheet(SHEET_NO, sheetName).head(targetClazz).build();
                excelWriter.write(expData, sheet1);
                excelWriter.finish();
            } catch (Exception e) {
                throw new CommonExecption(GlobalStatusCode.EXPORT_EXCEL_ERROR);
            }
            log.info("导出成功！数据已经导出到第" + SHEET_NO + "个工作簿，工作簿名称为：" + sheetName + "，共" + expData.size() + "条记录！");
        }
        return o;
    }
}